package com.ldy.core.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Authenticator;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

/**
 * 请求工具
 * 
 * @author lindeyi
 *
 */
public class RequestUtil {
	private static final Logger logger = LoggerFactory.getLogger(RequestUtil.class);

	public static void main(String[] args) {
		String http = getHttp("http://ipinfo.io", null, null);
		System.out.println(http);

	}

	public static String getHttp(String url, Map<String, String> headers, List<NameValuePair> nvps) {
		return getHttp(url, headers, nvps, null, 0, null, null, false, null);
	}

	/**
	 * get 请求
	 * 
	 * @param url  请求地址
	 * @param nvps 参数列表
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String getHttp(String url, Map<String, String> headers, List<NameValuePair> nvps, String proxyIp,
			int port, String username, String pwd, boolean isSocks, String localIp) {
		String responseStr = null;

		CloseableHttpResponse response = null;
		CloseableHttpClient httpclient = null;
		try {
			if (nvps != null && !nvps.isEmpty()) {
				String params = EntityUtils.toString(new UrlEncodedFormEntity(nvps, Charset.forName("UTF-8")));
				url = url + '?' + params;
			}
			HttpGet httpGet = new HttpGet(url);
			if (headers != null && !headers.isEmpty()) {
				headers.forEach((k, v) -> {
					httpGet.setHeader(k, v);
				});
			}
			SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(
					SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
					NoopHostnameVerifier.INSTANCE);

			HttpClientBuilder build = HttpClients.custom().setSSLSocketFactory(scsf);
			// 设置出口IP
			if (!StringUtils.isEmpty(localIp)) {
				String localAddress = localIp;
				String[] ipStr = localAddress.split("\\.");
				byte[] localAddressByte = new byte[4];
				for (int i = 0; i < 4; i++) {
					localAddressByte[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
				}
				RequestConfig config = RequestConfig.custom()
						.setLocalAddress(InetAddress.getByAddress(localAddressByte)).build();
				httpGet.setConfig(config);
			}
			// 没有代理的， 就不走socks模式
			if (!isSocks || StringUtils.isEmpty(proxyIp)) {
				response = setHttpProxy(httpGet, build, proxyIp, port, username, pwd);
			} else {
				response = setSocksProxy(httpGet, build, proxyIp, port, username, pwd);
			}
			System.out.println(httpGet);
			System.out.println(response);
			logger.info("===>response status:{}", response.getStatusLine().getStatusCode());
			HttpEntity entity = response.getEntity();
			responseStr = EntityUtils.toString(entity);
			EntityUtils.consume(entity);
		} catch (Exception e) {
			logger.error("请求异常", e);
		} finally {
			if (null != response) {
				try {
					response.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != httpclient) {
				try {
					httpclient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return responseStr;
	}

	public static String postJsonHttp(String url, Map<String, String> headers, String data) {
		return postJsonHttp(url, headers, data, null, 0, null, null, false, null);
	}

	/**
	 * post 请求
	 * 
	 * @param url  请求地址
	 * @param nvps 参数列表
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String postJsonHttp(String url, Map<String, String> headers, String data, String proxyIp, int port,
			String username, String pwd, boolean isSocks, String localIp) {
		String responseStr = null;
		CloseableHttpResponse response = null;
		CloseableHttpClient httpclient = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			if (headers != null && !headers.isEmpty()) {
				headers.forEach((k, v) -> {
					httpPost.setHeader(k, v);
				});
			}
			if (StringUtils.hasText(data)) {
				httpPost.setEntity(new StringEntity(data));
			}
			SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(
					SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
					NoopHostnameVerifier.INSTANCE);

			HttpClientBuilder build = HttpClients.custom().setSSLSocketFactory(scsf);
			// 设置出口IP
			if (!StringUtils.isEmpty(localIp)) {
				String localAddress = localIp;
				String[] ipStr = localAddress.split("\\.");
				byte[] localAddressByte = new byte[4];
				for (int i = 0; i < 4; i++) {
					localAddressByte[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
				}
				RequestConfig config = RequestConfig.custom()
						.setLocalAddress(InetAddress.getByAddress(localAddressByte)).build();
				httpPost.setConfig(config);
			}

			// 没有代理的， 就不走socks模式
			if (!isSocks || StringUtils.isEmpty(proxyIp)) {
				response = setHttpProxy(httpPost, build, proxyIp, port, username, pwd);
			} else {
				response = setSocksProxy(httpPost, build, proxyIp, port, username, pwd);
			}

			logger.info("===>response status:{}", response.getStatusLine().getStatusCode());
			HttpEntity entity = response.getEntity();
			responseStr = EntityUtils.toString(entity);
			EntityUtils.consume(entity);
		} catch (Exception e) {
			logger.error("请求异常", e);
		} finally {
			if (null != response) {
				try {
					response.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != httpclient) {
				try {
					httpclient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return responseStr;
	}

	public static String postHttp(String url, Map<String, String> headers, List<NameValuePair> nvps) {
		return postHttp(url, headers, nvps, null, 0, null, null, false, null);
	}

	/**
	 * post 请求
	 * 
	 * @param url  请求地址
	 * @param nvps 参数列表
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String postHttp(String url, Map<String, String> headers, List<NameValuePair> nvps, String proxyIp,
			int port, String username, String pwd, boolean isSocks, String localIp) {
		String responseStr = null;
		CloseableHttpResponse response = null;
		CloseableHttpClient httpclient = null;
		try {
			HttpPost httpPost = new HttpPost(url);
			if (headers != null && !headers.isEmpty()) {
				headers.forEach((k, v) -> {
					httpPost.setHeader(k, v);
				});
			}
			// 重要！！ 指定编码，对中文进行编码
			httpPost.setEntity(new UrlEncodedFormEntity(nvps, Charset.forName("UTF-8")));
			SSLConnectionSocketFactory scsf = new SSLConnectionSocketFactory(
					SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
					NoopHostnameVerifier.INSTANCE);

			HttpClientBuilder build = HttpClients.custom().setSSLSocketFactory(scsf);
			// 设置出口IP
			if (!StringUtils.isEmpty(localIp)) {
				String localAddress = localIp;
				String[] ipStr = localAddress.split("\\.");
				byte[] localAddressByte = new byte[4];
				for (int i = 0; i < 4; i++) {
					localAddressByte[i] = (byte) (Integer.parseInt(ipStr[i]) & 0xff);
				}
				RequestConfig config = RequestConfig.custom()
						.setLocalAddress(InetAddress.getByAddress(localAddressByte)).build();
				httpPost.setConfig(config);
			}

			// 没有代理的， 就不走socks模式
			if (!isSocks || StringUtils.isEmpty(proxyIp)) {
				response = setHttpProxy(httpPost, build, proxyIp, port, username, pwd);
			} else {
				response = setSocksProxy(httpPost, build, proxyIp, port, username, pwd);
			}

			logger.info("===>response status:{}", response.getStatusLine().getStatusCode());
			HttpEntity entity = response.getEntity();
			responseStr = EntityUtils.toString(entity);
			EntityUtils.consume(entity);
		} catch (Exception e) {
			logger.error("请求异常", e);
		} finally {
			if (null != response) {
				try {
					response.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != httpclient) {
				try {
					httpclient.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return responseStr;
	}

	/**
	 * http 代理
	 * 
	 * @param httpPost
	 * @param build
	 * @param proxyIp
	 * @param port
	 * @param username
	 * @param pwd
	 * @return
	 * @throws Exception
	 */
	private static CloseableHttpResponse setHttpProxy(HttpRequestBase httpPost, HttpClientBuilder build, String proxyIp,
			int port, String username, String pwd) throws Exception {
		if (!StringUtils.isEmpty(proxyIp)) {
			logger.info("使用http代理: proxyIp:{}, port:{}, username: {}, pwd: {}", proxyIp, port, username, pwd);
			// 使用代理
			CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
			if (!StringUtils.isEmpty(username)) {
				credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, pwd));
			}
			HttpHost proxy = new HttpHost(proxyIp, port, "http");
			build.setProxy(proxy).setDefaultCredentialsProvider(credentialsProvider);
		}
		CloseableHttpClient httpclient = build.build();

		return httpclient.execute(httpPost);
	}

	/**
	 * socks 代理
	 * 
	 * @param httpPost
	 * @param build
	 * @param proxyHost
	 * @param proxyPort
	 * @param proxyName
	 * @param proxyPwd
	 * @return
	 * @throws Exception
	 */
	private static CloseableHttpResponse setSocksProxy(HttpRequestBase httpPost, HttpClientBuilder build,
			String proxyHost, int proxyPort, String proxyName, String proxyPwd) throws Exception {
		logger.info("使用socks代理: proxyIp:{}, port:{}, username: {}, pwd: {}", proxyHost, proxyPort, proxyName, proxyPwd);
		// 用户名和密码验证
		Authenticator.setDefault(new Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				PasswordAuthentication p = new PasswordAuthentication(proxyName, proxyPwd.toCharArray());
				return p;
			}
		});
		Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("http", new MyConnectionSocketFactory())
				.register("https", new MySSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build();
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
		CloseableHttpClient httpclient = build.setConnectionManager(cm).build();
		InetSocketAddress socksaddr = new InetSocketAddress(proxyHost, proxyPort);
		HttpClientContext context = HttpClientContext.create();
		context.setAttribute("socks.address", socksaddr);
		return httpclient.execute(httpPost, context);
	}

	static class FakeDnsResolver implements DnsResolver {
		@Override
		public InetAddress[] resolve(String host) throws UnknownHostException {
			// Return some fake DNS record for every request, we won't be using it
			return new InetAddress[] { InetAddress.getByAddress(new byte[] { 1, 1, 1, 1 }) };
		}
	}

	static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
		@Override
		public Socket createSocket(final HttpContext context) throws IOException {
			Socket socket = new Socket();
			InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
			socket.bind(socksaddr);
			return socket;
		}

		@Override
		public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
				InetSocketAddress localAddress, HttpContext context) throws IOException {
			if (socket == null) {
				socket = createSocket(context);
			}
			if (localAddress != null) {
				socket.bind(localAddress);
			}
			socket.connect(remoteAddress, connectTimeout);
			return socket;
		}
	}

	static class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

		public MySSLConnectionSocketFactory(final SSLContext sslContext) {
			// You may need this verifier if target site's certificate is not secure
			super(sslContext, NoopHostnameVerifier.INSTANCE);
		}

		@Override
		public Socket createSocket(final HttpContext context) throws IOException {
			Socket socket = new Socket();
			InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
			socket.bind(socksaddr);
			return socket;
		}
		@Override
		 public Socket connectSocket(
                 int connectTimeout,
                 Socket socket,
                 HttpHost host,
                 InetSocketAddress remoteAddress,
                 InetSocketAddress localAddress,
                 HttpContext context) throws IOException {
             if (socket == null) {
                 socket = createSocket(context);
             }
             if (localAddress != null) {
                 socket.bind(localAddress);
             }
             socket.connect(remoteAddress, connectTimeout);
             return socket;
         }
	}
}
